﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

namespace NuMvvm.Commands
{
    public class AsyncDelegateCommand : ICommand
    {
        private readonly Func<CancellationToken, Task<bool>> _canExecuteAsync;
        private readonly Func<CancellationToken, Task> _executeAsync;
        private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

        public AsyncDelegateCommand(Func<CancellationToken, Task> execute, Func<CancellationToken, Task<bool>> canExecute)
        {
            _executeAsync = execute;
            _canExecuteAsync = canExecute;
        }

        public AsyncDelegateCommand(Func<CancellationToken, Task> execute)
        {
            _executeAsync = execute;
            _canExecuteAsync = null;
        }

        public Task<bool> CanExecuteAsync()
        {
            if (_canExecuteAsync == null)
            {
                return Task.FromResult(true);
            }

            return Task.Run(() => _canExecuteAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
        }

        public Task ExecuteAsync()
        {
            return Task.Run(() => _executeAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
        }

        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }

        #region Implements ICommand 

        public event EventHandler CanExecuteChanged;

        bool ICommand.CanExecute(object parameter)
        {
            var task = CanExecuteAsync();
            return task.Result;
        }

        async void ICommand.Execute(object parameter)
        {
            await ExecuteAsync();
        }

        #endregion

        public void Cancel()
        {
            if (_cancellationTokenSource == null)
                throw new InvalidOperationException();

            _cancellationTokenSource.Cancel();
        }

        public void CancelAfter(int millisecondsDelay)
        {
            if (_cancellationTokenSource == null)
                throw new InvalidOperationException();

            _cancellationTokenSource.CancelAfter(millisecondsDelay);
        }

        public void CancelAfter(TimeSpan timeSpan)
        {
            CancelAfter(timeSpan);
        }
    }
}
